package com.zzyl.nursing.service.impl;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.utils.DateUtils;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.system.mapper.SysUserRoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.service.IAlertRuleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

/**
 * 报警规则Service业务层处理
 * 
 * @author alexis
 * @date 2025-06-09
 */
@Service
@Slf4j
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService
{
    @Autowired
    private AlertRuleMapper alertRuleMapper;

    @Autowired
    private RedisTemplate<String ,String> redisTemplate;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;

    @Autowired
    private IAlertDataService alertDataService;

    /**
     * 查询报警规则
     * 
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id)
    {
        return alertRuleMapper.selectById(id);
    }

    /**
     * 查询报警规则列表
     * 
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule)
    {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule)
    {
        return alertRuleMapper.insert(alertRule);
    }

    /**
     * 修改报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule)
    {
        return alertRuleMapper.updateById(alertRule);
    }

    /**
     * 批量删除报警规则
     * 
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids)
    {
        return alertRuleMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除报警规则信息
     * 
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id)
    {
        return alertRuleMapper.deleteById(id);
    }

    /**
     * 上报数据过滤
     */
    @Override
    public void alertFilter() {
        // 查询所有的规则
        long count = count(Wrappers.<AlertRule>lambdaQuery()
                .eq(AlertRule::getStatus, 1));
        if (count <= 0 ) {
            //没有生效的规则，直接返回即可
            return;
        }

        // 查询所有上报的数据，将所有设备的数据都查询出来
        List<Object> values = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA);
        if (CollUtil.isEmpty(values)) {
            // 上报数据为空，则结束此次定时任务
            return;
        }

        // 将查询到的数据进行解析，将所有的设备数据保存到一个集合中
        List<DeviceData> deviceDataList = new ArrayList<>();
        // 将每一条JSON数据转换为DeviceData对象,并将数据(每一个设备的每一个物模型的数据)存储到一个集合中
        values.forEach(value -> deviceDataList.addAll(JSONUtil.toList(value.toString(), DeviceData.class)));

        // 双重遍历，遍历规则和上报数据，将每一条上报数据与每一条规则进行匹配与核对
        deviceDataList.forEach(d -> alertFilter(d));
    }

    /**
     * 过滤上报数据是否符合报警规则
     * @param deviceData，每个产品每个物模型的数据
     */
    private void alertFilter(DeviceData deviceData) {
        // 判断当前数据的上报使时间距离现在是否有超过1分钟

        // 数据上报时间
        LocalDateTime alarmTime = deviceData.getAlarmTime();
        long between = LocalDateTimeUtil.between(alarmTime, LocalDateTime.now(), ChronoUnit.SECONDS);
        if (between > 60) {
            // 上报时间超过1分钟，为历史数据，不需要进行报警，则直接返回
            return;
        }

        //  查询和该设备匹配的规则
        List<AlertRule> allRules = list(Wrappers.<AlertRule>lambdaQuery()
                .eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getIotId, deviceData.getIotId())
                .eq(AlertRule::getStatus, "-1") // 所有设备的规则
                .eq(AlertRule::getStatus, 1));
        // 查询和该物模型和该设备匹配的规则
        List<AlertRule> iotIdRules = list(Wrappers.<AlertRule>lambdaQuery()
                .eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getIotId, deviceData.getIotId())
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId()) // 匹配该设备物模型的规则
                .eq(AlertRule::getStatus, 1));
        // 将查询到的规则合并起来
        Collection<AlertRule> alertRules = CollUtil.addAll(allRules, iotIdRules);
        if (CollUtil.isEmpty(alertRules)) {
            // 如果规则为空，则直接结束
            return;
        }

        // 遍历规则，将每一条上报数据与每一条规则进行匹配与核对
        alertRules.forEach(rule -> deviceDataAlarmHandler(rule,deviceData));


    }

    /**
     * 设备数据与规则进行匹配与核对
     * @param rule 报警规则
     * @param deviceData 设备数据
     */
    private void deviceDataAlarmHandler(AlertRule rule, DeviceData deviceData) {
        // 第一层校验： 数据上报时间是否在规则生效时间内
        // 从规则中获取生效时间
        String[] split = rule.getAlertEffectivePeriod().split("~");
        LocalTime  startTime = LocalTime.parse(split[0]);
        LocalTime  endTime = LocalTime.parse(split[1]);
        // 获取数据上报时间
        LocalTime alertTime = LocalDateTimeUtil.of(deviceData.getAlarmTime()).toLocalTime();
        if (alertTime.isBefore(startTime) || alertTime.isAfter(endTime)) {
            // 如果数据上报时间不在规则生效时间内，则直接结束
            return;
        }

        // 获取上报数据中的iotId
        String iotId = deviceData.getIotId();
        // 设计redis中报警次数的key: 常量+iotId+物模型id+报警规则id
        String alertCountKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX+ iotId +":"+ deviceData.getFunctionId() +":"+ rule.getId();

        // 第二层校验： 上报数据数值与规则阈值进行比较
        // 获取上报数据与阈值的差值,如果上报数据大于阈值数据则返回1，小于则返回-1，等于则返回0
        int compare = NumberUtil.compare(Double.valueOf(deviceData.getDataValue()), rule.getValue());
        if ((rule.getOperator().equals(">=")&& compare < 0)||  (rule.getOperator().equals("<")&& compare >= 0)) {
            // 上报数据不符合阈值范围，则直接返回
            // 同时删除缓存中的报警次数
            redisTemplate.delete(alertCountKey);
            return;
        }

        // 此处设备上报数据已经异常，进行第三层校验
        // 第三层校验： 判断此次报警是否在沉默周期内
        // 设计缓存中数据沉默周期的key:常量+iotId+物模型id+报警规则id
        String alertSilentKey = CacheConstants.ALERT_SILENT_PREFIX+ iotId +":"+ deviceData.getFunctionId() +":"+ rule.getId();
        String silentData = redisTemplate.opsForValue().get(alertSilentKey);
        if (StrUtil.isNotEmpty(silentData)) {
            // 存在沉默周期，则直接返回
            return;
        }

        // 不存在沉默周期，即可进行第四层校验
        // 第四层校验： 判断报警次数是否满足持续周期
        // 查询已经报警的次数
        String alertCount = redisTemplate.opsForValue().get(alertCountKey);
        // 将报警次数直接+1
        int count = StrUtil.isEmpty(alertCount) ? 1 : Integer.parseInt(alertCount) + 1;
        if (ObjectUtil.notEqual(count, rule.getDuration())) {
            // 加上本次报警，与持续周期不相等，则直接将增加后的报警次数保存至redis中,然后直接返回
            redisTemplate.opsForValue().set(alertCountKey,count+"");
            return;
        }

        // 计算上本次报警次数，满足持续周期，则进行报警，同时将redis中的报警次数删除，同时设置沉默周期
        redisTemplate.delete(alertCountKey);
        redisTemplate.opsForValue().set(alertSilentKey,"1",rule.getAlertSilentPeriod(), TimeUnit.MINUTES);

        // 数据判断完满足报警规则，获取异常数据相关人员id
        List<Long> userIds = new ArrayList<>();
        // 判断报警数据异常类型，0：老人异常数据，找相关护理人员处理，1：设备异常数据，找维修共或者行政人员处理
        if (rule.getAlertDataType().equals(0)) {
            // 老人数据异常，获取相关护理人员id
            // 判断数据是类型是随身设备还是固定设备
            if (deviceData.getLocationType().equals(0)) {
                // 随身设备数据
                userIds = deviceMapper.selectNursingIdsByIotIdWithElder(iotId);
            } else if (deviceData.getLocationType().equals(1)) {
                // 固定设备数据
                userIds = deviceMapper.selectNursingIdsByIotIdWithBed(iotId);
            }
        } else {
            // 设备数据异常，获取相关维修人员或者行政人员id
            userIds = sysUserRoleMapper.selectUserIdByRoleName("维修工");
        }
        // 有异常数据均需要通知超级管理员
        List<Long> masterIds = sysUserRoleMapper.selectUserIdByRoleName("超级管理员");
        Collection<Long> allUserIds = CollUtil.addAll(userIds, masterIds);
        allUserIds = CollUtil.distinct(allUserIds);

        // 保存上报数据
        insertAlertRule(allUserIds,rule,deviceData);
    }

    /**
     * 保存异常数据
     * @param allUserIds
     * @param rule
     * @param deviceData
     */
    private void insertAlertRule(Collection<Long> allUserIds, AlertRule rule, DeviceData deviceData) {
        // 属性拷贝，将设备数据中属性拷贝到报警数据中
        AlertData alertData = BeanUtil.toBean(deviceData, AlertData.class);
        // 设置上一步拷贝操作设置的报警数据中的主键id
        alertData.setId(null);
        // 设置报警数据中的规则id
        alertData.setAlertRuleId(rule.getId());
        // 组织报警原因：报警的物模型+运算符+报警阈值+持续周期
        String alertReason = CharSequenceUtil.format("{}{}{},持续{}几个周期报警", rule.getFunctionId(), rule.getOperator(), rule.getValue(), rule.getDuration());
        // 设置报警原因
        alertData.setAlertReason(alertReason);
        // 设置报警信息状态为未处理
        alertData.setStatus(0);
        // 设置报警类型
        alertData.setType(rule.getAlertDataType());
        // 遍历用户id,为每个用户保存一份报警数据
        List<AlertData> alertDataList = allUserIds.stream().map(userId -> {
            // 定义一个用户的报警数据
            AlertData usrAlertDate = BeanUtil.toBean(alertData, AlertData.class);
            // 为当前数据设置用户id
            usrAlertDate.setUserId(userId);
            // 设置主键id为空，避免上面拷贝操作设置主键id
            usrAlertDate.setId(null);
            return usrAlertDate;
        }).collect(Collectors.toList());

        // 批量保存报警数据
        alertDataService.saveBatch(alertDataList);


    }
}
